{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Copyright 2018 Google LLC  \n",
    "  \n",
    " Licensed under the Apache License, Version 2.0 (the \"License\");  \n",
    " you may not use this file except in compliance with the License.  \n",
    " You may obtain a copy of the License at  \n",
    "  \n",
    "     http://www.apache.org/licenses/LICENSE-2.0  \n",
    "  \n",
    " Unless required by applicable law or agreed to in writing, software  \n",
    " distributed under the License is distributed on an \"AS IS\" BASIS,  \n",
    " WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  \n",
    " See the License for the specific language governing permissions and  \n",
    " limitations under the License."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Only for when running on Colab:\n",
    "import sys\n",
    "if 'google.colab' in sys.modules:\n",
    "    # Get the dependency .py files, if any.\n",
    "    ! git clone https://github.com/GoogleCloudPlatform/cloudml-samples.git\n",
    "    ! cp cloudml-samples/tpu/templates/tpu_estimator/* .\n",
    "\n",
    "    # Authenticate the user for better GCS access.\n",
    "    # Copy verification code into the text field to continue.\n",
    "    from google.colab import auth\n",
    "    auth.authenticate_user()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import argparse\n",
    "import numpy as np\n",
    "import tensorflow as tf"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The model function  \n",
    " There are two differences in the model function when using TPUs:  \n",
    "   \n",
    " * The optimizer needs to be wrapped in a `tf.contrib.tpu.CrossShardOptimizer`.  \n",
    "  \n",
    " * The model function should return a `tf.contrib.tpu.TPUEstimatorSpec`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def model_fn(features, labels, mode, params):\n",
    "    # build model\n",
    "    global_step = tf.train.get_global_step()\n",
    "    hidden = tf.layers.dense(features, 10, activation=tf.nn.relu)\n",
    "    output = tf.layers.dense(hidden, 1)\n",
    "\n",
    "    predictions = output\n",
    "    loss = None\n",
    "    train_op = None\n",
    "\n",
    "    if mode == tf.estimator.ModeKeys.TRAIN:\n",
    "        # define loss\n",
    "        loss = tf.nn.l2_loss(predictions - labels)\n",
    "\n",
    "        # define train_op\n",
    "        optimizer = tf.train.RMSPropOptimizer(learning_rate=0.05)\n",
    "\n",
    "        # wrapper to make the optimizer work with TPUs\n",
    "        if params['use_tpu']:\n",
    "            optimizer = tf.contrib.tpu.CrossShardOptimizer(optimizer)\n",
    "\n",
    "        train_op = optimizer.minimize(loss, global_step=global_step)\n",
    "\n",
    "    if params['use_tpu']:\n",
    "        # TPU version of EstimatorSpec\n",
    "        return tf.contrib.tpu.TPUEstimatorSpec(\n",
    "            mode=mode,\n",
    "            predictions=predictions,\n",
    "            loss=loss,\n",
    "            train_op=train_op)\n",
    "    else:\n",
    "        return tf.estimator.EstimatorSpec(\n",
    "            mode=mode,\n",
    "            predictions=predictions,\n",
    "            loss=loss,\n",
    "            train_op=train_op)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The input function  \n",
    " tf.data.Dataset is the best choice for building the input function.  \n",
    " Even though datasets can determine the shape of the data at *runtime*,  \n",
    " TPUs need to know the shape of the tensors *when the graph is built*.  \n",
    " This typically means two things:  \n",
    "  \n",
    " * Set `drop_remainder=True` in the `dataset.batch` call.  \n",
    "  \n",
    " * Set tensor shapes to make sure the features and labels do not have any unknown dimensions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def train_input_fn(params={}):\n",
    "    # make some fake regression data\n",
    "    x = np.random.rand(100, 5)\n",
    "    w = np.random.rand(5)\n",
    "    y = np.sum(x * w, axis=1)\n",
    "\n",
    "    # TPUs currently do not support float64\n",
    "    x_tensor = tf.constant(x, dtype=tf.float32)\n",
    "    y_tensor = tf.constant(y, dtype=tf.float32)\n",
    "\n",
    "    # create tf.data.Dataset\n",
    "    dataset = tf.data.Dataset.from_tensor_slices((x_tensor, y_tensor))\n",
    "\n",
    "    # TPUEstimator passes params when calling input_fn\n",
    "    batch_size = params.get('batch_size', 16)\n",
    "\n",
    "    dataset = dataset.repeat().shuffle(32).batch(batch_size, drop_remainder=True)\n",
    "\n",
    "    # TPUs need to know all dimensions when the graph is built\n",
    "    # Datasets know the batch size only when the graph is run\n",
    "    def set_shapes(features, labels):\n",
    "        features_shape = features.get_shape().merge_with([batch_size, None])\n",
    "        labels_shape = labels.get_shape().merge_with([batch_size])\n",
    "\n",
    "        features.set_shape(features_shape)\n",
    "        labels.set_shape(labels_shape)\n",
    "\n",
    "        return features, labels\n",
    "\n",
    "    dataset = dataset.map(set_shapes)\n",
    "    dataset = dataset.prefetch(tf.contrib.data.AUTOTUNE)\n",
    "\n",
    "    return dataset"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The TPUEstimator  \n",
    " The TPUEstimator is similar to the usual Estimator, but requires a  \n",
    " slightly different run_config, since it needs to know where to connect  \n",
    " to the TPU workers.  \n",
    "  \n",
    " This is done through `tf.contrib.cluster_resolver.TPUClusterResolver`,  \n",
    " which is passed into a `tf.contrib.tpu.TPUConfig`, which in turn is  \n",
    " passed into `tf.contrib.tpu.RunConfig`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def main(args):\n",
    "    # pass the args as params so the model_fn can use\n",
    "    # the TPU specific args\n",
    "    params = vars(args)\n",
    "\n",
    "    if args.use_tpu:\n",
    "        # additional configs required for using TPUs\n",
    "        tpu_cluster_resolver = tf.contrib.cluster_resolver.TPUClusterResolver(args.tpu)\n",
    "        tpu_config = tf.contrib.tpu.TPUConfig(\n",
    "            num_shards=8, # using Cloud TPU v2-8\n",
    "            iterations_per_loop=args.save_checkpoints_steps)\n",
    "\n",
    "        # use the TPU version of RunConfig\n",
    "        config = tf.contrib.tpu.RunConfig(\n",
    "            cluster=tpu_cluster_resolver,\n",
    "            model_dir=args.model_dir,\n",
    "            tpu_config=tpu_config,\n",
    "            save_checkpoints_steps=args.save_checkpoints_steps,\n",
    "            save_summary_steps=100)\n",
    "\n",
    "        # TPUEstimator\n",
    "        estimator = tf.contrib.tpu.TPUEstimator(\n",
    "            model_fn=model_fn,\n",
    "            config=config,\n",
    "            params=params,\n",
    "            train_batch_size=args.train_batch_size,\n",
    "            eval_batch_size=32,\n",
    "            export_to_tpu=False)\n",
    "    else:\n",
    "        config = tf.estimator.RunConfig(model_dir=args.model_dir)\n",
    "\n",
    "        estimator = tf.estimator.Estimator(\n",
    "            model_fn,\n",
    "            config=config,\n",
    "            params=params)\n",
    "\n",
    "    estimator.train(train_input_fn, max_steps=args.max_steps)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Training  \n",
    " Depending on where the training job is run, the `TPUClusterResolver`  \n",
    " needs different input to access the TPU workers:  \n",
    "  \n",
    " * On AI Platform: the input should be `None`   \n",
    "   and the service will handle it.  \n",
    "  \n",
    " * On Compute Engine: the input should be the name of TPU you create  \n",
    "   before starting the training job.  \n",
    "  \n",
    " * On Colab: the input should be the grpc URI from the environment  \n",
    " variable `COLAB_TPU_ADDR`; the Colab runtime type should be set to  \n",
    " TPU for this environment variable to be automatically set."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "parser = argparse.ArgumentParser()\n",
    "\n",
    "parser.add_argument(\n",
    "    '--model-dir',\n",
    "    type=str,\n",
    "    default='/tmp/tpu-template',\n",
    "    help='Location to write checkpoints and summaries to.  Must be a GCS URI when using Cloud TPU.')\n",
    "parser.add_argument(\n",
    "    '--max-steps',\n",
    "    type=int,\n",
    "    default=1000,\n",
    "    help='The total number of steps to train the model.')\n",
    "parser.add_argument(\n",
    "    '--train-batch-size',\n",
    "    type=int,\n",
    "    default=16,\n",
    "    help='The training batch size.  The training batch is divided evenly across the TPU cores.')\n",
    "parser.add_argument(\n",
    "    '--save-checkpoints-steps',\n",
    "    type=int,\n",
    "    default=100,\n",
    "    help='The number of training steps before saving each checkpoint.')\n",
    "parser.add_argument(\n",
    "    '--use-tpu',\n",
    "    action='store_true',\n",
    "    help='Whether to use TPU.')\n",
    "parser.add_argument(\n",
    "    '--tpu',\n",
    "    default=None,\n",
    "    help='The name or GRPC URL of the TPU node.  Leave it as `None` when training on AI Platform.')\n",
    "\n",
    "args, _ = parser.parse_known_args()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# colab.research.google.com specific\n",
    "if 'google.colab' in sys.modules:\n",
    "    import json\n",
    "    import os\n",
    "\n",
    "    # TODO(user): change this\n",
    "    args.model_dir = 'gs://your-gcs-bucket'\n",
    "\n",
    "    # When connected to the TPU runtime\n",
    "    if 'COLAB_TPU_ADDR' in os.environ:\n",
    "        tpu_grpc = 'grpc://{}'.format(os.environ['COLAB_TPU_ADDR'])\n",
    "\n",
    "        args.tpu = tpu_grpc\n",
    "        args.use_tpu = True\n",
    "\n",
    "        # Upload credentials to the TPU\n",
    "        with tf.Session(tpu_grpc) as sess:\n",
    "            data = json.load(open('/content/adc.json'))\n",
    "            tf.contrib.cloud.configure_gcs(sess, credentials=data)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "main(args)"
   ]
  }
 ],
 "metadata": {},
 "nbformat": 4,
 "nbformat_minor": 2
}
